﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.IO;

namespace Weka
{
    internal static class Extensions
    {
        public static TValue TryGet<TKey, TValue>(this Dictionary<TKey, TValue> dic, TKey key)
        {
            TValue v;
            if (dic.TryGetValue(key, out v))
                return v;
            else
                return default(TValue);
        }




        public static IEnumerable<PropertyInfo> GetAllProperties(this Type type)
        {
            if (type.BaseType != typeof(object))
            {
                foreach (var p in GetAllProperties(type.BaseType))
                    yield return p;
            }


            foreach (var p in type.GetProperties(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public))
                yield return p;
        }

        public static IEnumerable<MethodInfo> GetAllMethods(this Type type)
        {
            if (type.BaseType != typeof(object))
            {
                foreach (var m in GetAllMethods(type.BaseType))
                    yield return m;
            }


            foreach (var m in type.GetMethods(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public))
                yield return m;
        }

        public static Type GetIEnumerableType(this Type type)
        {
            if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IEnumerable<>))
                return type.GetGenericArguments()[0];

            foreach (var it in type.GetInterfaces())
            {
                if (!it.IsGenericType) continue;
                if (it.GetGenericTypeDefinition() == typeof(IEnumerable<>))
                    return it.GetGenericArguments()[0];
            }
            return null;
        }
        public static bool IsNullable(this Type type)
        {
            return (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>));// t = t.GetGenericArguments()[0];
        }
        public static Type GetNullableType(this Type type)
        {
            return type.GetGenericArguments()[0];
        }
        public static bool IsAMessage(this Type type)
        {
            return type != null && (type.GetCustomAttributes(typeof(MessageIdAttribute), false).Length > 0);
        }
        public static byte GetMessageId(this Type type)
        {
            return ((MessageIdAttribute)type.GetCustomAttributes(typeof(MessageIdAttribute), false)[0]).Id;
        }
        public static Func<TV, TR> CreateDelegate<TV, TR>(this MethodInfo methodInfo)
        {
            return (Func<TV, TR>)Delegate.CreateDelegate(typeof(Func<TV, TR>), null, methodInfo);
        }
        public static Action<TV, TR> CreateActionDelegate<TV, TR>(this MethodInfo methodInfo)
        {
            if (methodInfo == null)
                return (tv, tr) => { };
            else if (methodInfo.GetParameters()[0].ParameterType != typeof(TR))
                return CreateErrorAction<TV, TR>(methodInfo);
            else
                return (Action<TV, TR>)Delegate.CreateDelegate(typeof(Action<TV, TR>), null, methodInfo);
        }

        public static Action<T> CreateActionDelegate<T>(this MethodInfo methodInfo)
        {
            return (Action<T>)Delegate.CreateDelegate(typeof(Action<T>), null, methodInfo);
        }

        private static Action<TV, TR> CreateErrorAction<TV, TR>(MethodInfo methodInfo)
        {
            string ms = string.Format("Type Mismatch. Can not assign {0} to {1}.{2}({3}).", typeof(TR), methodInfo.DeclaringType, methodInfo.Name, methodInfo.GetParameters()[0].ParameterType);
            return (tv, tr) => { throw new Exception(ms); };
        }

        public static DataType GetDataType(this Type t)
        {
            if (t == typeof(string)) return DataType.String;
            if (t == typeof(bool?)) return DataType.BooleanNullable;

            if (t.IsNullable()) t = t.GetNullableType();

            if (t.IsEnum) t = Enum.GetUnderlyingType(t);

            if (t == typeof(DateTime)) return DataType.DateTime;
            if (t == typeof(bool)) return DataType.Boolean;
            if (t == typeof(byte)) return DataType.Byte;
            if (t == typeof(Decimal)) return DataType.Decimal;
            if (t == typeof(Double)) return DataType.Double;
            if (t == typeof(Guid)) return DataType.Guid;
            if (t == typeof(Int16)) return DataType.Int16;
            if (t == typeof(Int32)) return DataType.Int32;
            if (t == typeof(Int64)) return DataType.Int64;
            if (t == typeof(Single)) return DataType.Single;
            if (t == typeof(TimeSpan)) return DataType.TimeSpan;
            if (t == typeof(UInt16)) return DataType.UInt16;
            if (t == typeof(UInt32)) return DataType.UInt32;
            if (t == typeof(UInt64)) return DataType.UInt64;

            if (t == typeof(Func<Stream>)) return DataType.FuncStream;

            throw new NotImplementedException("Serlization of type '" + t.Name + "' is not implemented.");//TODO give more details.
        }


        public static Type GetSetType(this MethodInfo methodInfo)
        {
            return methodInfo.GetParameters()[0].ParameterType;
        }


        #region Enum stuff



        public static Func<TV, Nullable<TFV>> CreateEnumNullableDelegate<TV, TFV>(this MethodInfo methodInfo)
            where TFV : struct
        {
            MethodInfo methodCreateEnumDelegate3 = typeof(Extensions).GetMethod("CreateEnumDelegate3", BindingFlags.Static | BindingFlags.NonPublic);
            MethodInfo meth1 = methodCreateEnumDelegate3.MakeGenericMethod(typeof(TV), methodInfo.ReturnType.GetNullableType(), typeof(TFV));
            return (Func<TV, Nullable<TFV>>)meth1.Invoke(null, new object[] { methodInfo });
        }

        public static Func<TV, TFV> CreateEnumDelegate<TV, TFV>(this MethodInfo methodInfo)
            where TFV : struct
        {
            MethodInfo methodCreateEnumDelegate2 = typeof(Extensions).GetMethod("CreateEnumDelegate2", BindingFlags.Static | BindingFlags.NonPublic);
            MethodInfo meth1 = methodCreateEnumDelegate2.MakeGenericMethod(typeof(TV), methodInfo.ReturnType, typeof(TFV));
            return (Func<TV, TFV>)meth1.Invoke(null, new object[] { methodInfo });
        }

        private static Func<TV, TFV> CreateEnumDelegate2<TV, TE, TFV>(this MethodInfo methodInfo)
            where TFV : struct
        {
            var del = (Func<TV, TE>)Delegate.CreateDelegate(typeof(Func<TV, TE>), null, methodInfo);

            return t => (TFV)Convert.ChangeType(del(t), typeof(TFV), null);
        }

        private static Func<TV, Nullable<TFV>> CreateEnumDelegate3<TV, TE, TFV>(this MethodInfo methodInfo)
            where TFV : struct
            where TE : struct
        {
            var del = (Func<TV, Nullable<TE>>)Delegate.CreateDelegate(typeof(Func<TV, Nullable<TE>>), null, methodInfo);

            return t =>
            {
                var v = del(t);
                return (!v.HasValue) ? default(Nullable<TFV>) : (TFV)Convert.ChangeType(v, typeof(TFV), null);
            };
        }


        public static Action<TV, Nullable<TFV>> CreateEnumNullableSetterDelegate<TV, TFV>(this MethodInfo methodInfo)
    where TFV : struct
        {
            Type te = methodInfo.GetParameters()[0].ParameterType.GetNullableType();
            MethodInfo methodCreateEnumDelegate5 = typeof(Extensions).GetMethod("CreateSetEnumDelegate5", BindingFlags.Static | BindingFlags.NonPublic);
            MethodInfo meth1 = methodCreateEnumDelegate5.MakeGenericMethod(typeof(TV), te, typeof(TFV));
            return (Action<TV, Nullable<TFV>>)meth1.Invoke(null, new object[] { methodInfo });
        }

        private static Action<TV, Nullable<TFV>> CreateSetEnumDelegate5<TV, TE, TFV>(this MethodInfo methodInfo)
            where TFV : struct
            where TE : struct
        {
            var del = (Action<TV, Nullable<TE>>)Delegate.CreateDelegate(typeof(Action<TV, Nullable<TE>>), null, methodInfo);

            return (TV v, TFV? fv) =>
            {
                TE? vts = (fv.HasValue) ? (TE)Enum.ToObject(typeof(TE), fv) : default(TE?);
                del(v, vts);
            };
        }


        public static Action<TV, TFV> CreateEnumSetterDelegate<TV, TFV>(this MethodInfo methodInfo)
    where TFV : struct
        {
            Type te = methodInfo.GetParameters()[0].ParameterType;
            MethodInfo methodCreateEnumDelegate6 = typeof(Extensions).GetMethod("CreateSetEnumDelegate6", BindingFlags.Static | BindingFlags.NonPublic);
            MethodInfo meth1 = methodCreateEnumDelegate6.MakeGenericMethod(typeof(TV), te, typeof(TFV));
            return (Action<TV, TFV>)meth1.Invoke(null, new object[] { methodInfo });
        }

        private static Action<TV, TFV> CreateSetEnumDelegate6<TV, TE, TFV>(this MethodInfo methodInfo)
            where TFV : struct
            where TE : struct
        {
            var del = (Action<TV, TE>)Delegate.CreateDelegate(typeof(Action<TV, TE>), null, methodInfo);

            return (TV v, TFV fv) =>
            {
                TE vts = (TE)Enum.ToObject(typeof(TE), fv);
                del(v, vts);
            };
        }



        #endregion



        public static void ReadFull(this Stream stream, byte[] buffer, int count)
        {
            int bytesStillToRead = count;
            while (bytesStillToRead > 0)
                bytesStillToRead -= stream.Read(buffer, count - bytesStillToRead, bytesStillToRead);
        }

    }
}
